2. The Basics
==========

NumPy's main object is the homogeneous multidimensional array. It is a
table of elements (usually numbers), all of the same type, indexed by a
tuple of positive integers. In Numpy dimensions are called *axes*. The
number of axes is *rank*.

For example, the coordinates of a point in 3D space ``[1, 2, 1]`` is an
array of rank 1, because it has one axis. That axis has a length of 3.
In example pictured below, the array has rank 2 (it is 2-dimensional).
The first dimension (axis) has a length of 2, the second dimension has a
length of 3.

::

[[ 1., 0., 0.],
[ 0., 1., 2.]]

Numpy's array class is called ``ndarray``. It is also known by the alias
``array``. Note that ``numpy.array`` is not the same as the Standard
Python Library class ``array.array``, which only handles one-dimensional
arrays and offers less functionality. The more important attributes of
an ``ndarray`` object are:

ndarray.ndim
the number of axes (dimensions) of the array. In the Python world,
the number of dimensions is referred to as *rank*.
ndarray.shape
the dimensions of the array. This is a tuple of integers indicating
the size of the array in each dimension. For a matrix with *n* rows
and *m* columns, ``shape`` will be ``(n,m)``. The length of the
``shape`` tuple is therefore the rank, or number of dimensions,
``ndim``.
ndarray.size
the total number of elements of the array. This is equal to the
product of the elements of ``shape``.
ndarray.dtype
an object describing the type of the elements in the array. One can
create or specify dtype's using standard Python types. Additionally
NumPy provides types of its own. numpy.int32, numpy.int16, and
numpy.float64 are some examples.
ndarray.itemsize
the size in bytes of each element of the array. For example, an
array of elements of type ``float64`` has ``itemsize`` 8 (=64/8),
while one of type ``complex32`` has ``itemsize`` 4 (=32/8). It is
equivalent to ``ndarray.dtype.itemsize``.
ndarray.data
the buffer containing the actual elements of the array. Normally, we
won't need to use this attribute because we will access the elements
in an array using indexing facilities.

2.1 An example
----------

>>> import numpy as np
>>> a = np.arange(15).reshape(3, 5)
>>> a
array([[ 0,  1,  2,  3,  4],
[ 5,  6,  7,  8,  9],
[10, 11, 12, 13, 14]])
>>> a.shape
(3, 5)
>>> a.ndim
2
>>> a.dtype.name
'int64'
>>> a.itemsize
8
>>> a.size
15
>>> type(a)
<type 'numpy.ndarray'>
>>> b = np.array([6, 7, 8])
>>> b
array([6, 7, 8])
>>> type(b)
<type 'numpy.ndarray'>


[demo]

import numpy as np
a = np.arange(15).reshape(3, 5)
print(a)
print(a.shape)
print(a.ndim)
print(a.dtype.name)
print(a.itemsize)
print(a.size)
b = np.array([6, 7, 8])
print(b)
print(type(b))

[/demo]


2.2 Array Creation
--------------

There are several ways to create arrays.

For example, you can create an array from a regular Python list or tuple
using the ``array`` function. The type of the resulting array is deduced
from the type of the elements in the sequences.

>>> import numpy as np
>>> a = np.array([2,3,4])
>>> a
array([2, 3, 4])
>>> a.dtype
dtype('int64')
>>> b = np.array([1.2, 3.5, 5.1])
>>> b.dtype
dtype('float64')

[demo]

import numpy as np
a = np.array([2,3,4])
print(a)
print(a.dtype)
b = np.array([1.2, 3.5, 5.1])
print(b.dtype)

[/demo]

A frequent error consists in calling ``array`` with multiple numeric
arguments, rather than providing a single list of numbers as an
argument.

::

>>> a = np.array(1,2,3,4)    # WRONG
>>> a = np.array([1,2,3,4])  # RIGHT

[demo]

import numpy as np
a = np.array(1,2,3,4)    # WRONG
a = np.array([1,2,3,4])  # RIGHT

[/demo]


``array`` transforms sequences of sequences into two-dimensional arrays,
sequences of sequences of sequences into three-dimensional arrays, and
so on.

>>> b = np.array([(1.5,2,3), (4,5,6)])
>>> b
array([[ 1.5,  2. ,  3. ],
[ 4. ,  5. ,  6. ]])

[demo]

import numpy as np
b = np.array([(1.5,2,3), (4,5,6)])
print(b)

[/demo]


The type of the array can also be explicitly specified at creation time:

::

>>> c = np.array( [ [1,2], [3,4] ], dtype=complex )
>>> c
array([[ 1.+0.j,  2.+0.j],
[ 3.+0.j,  4.+0.j]])

[demo]

import numpy as np
c = np.array( [ [1,2], [3,4] ], dtype=complex )
print(c)

[/demo]


Often, the elements of an array are originally unknown, but its size is
known. Hence, NumPy offers several functions to create
arrays with initial placeholder content. These minimize the necessity of
growing arrays, an expensive operation.

The function ``zeros`` creates an array full of zeros, the function
``ones`` creates an array full of ones, and the function ``empty``
creates an array whose initial content is random and depends on the
state of the memory. By default, the dtype of the created array is
``float64``.

>>> np.zeros( (3,4) )
array([[ 0.,  0.,  0.,  0.],
[ 0.,  0.,  0.,  0.],
[ 0.,  0.,  0.,  0.]])
>>> np.ones( (2,3,4), dtype=np.int16 )                # dtype can also be specified
array([[[ 1, 1, 1, 1],
[ 1, 1, 1, 1],
[ 1, 1, 1, 1]],
[[ 1, 1, 1, 1],
[ 1, 1, 1, 1],
[ 1, 1, 1, 1]]], dtype=int16)
>>> np.empty( (2,3) )                                 # uninitialized, output may vary
array([[  3.73603959e-262,   6.02658058e-154,   6.55490914e-260],
[  5.30498948e-313,   3.14673309e-307,   1.00000000e+000]])

[demo]

import numpy as np
print(np.zeros( (3,4) ))
print(np.ones( (2,3,4), dtype=np.int16 ) )
print(np.empty( (2,3) ) )

[/demo]


To create sequences of numbers, NumPy provides a function analogous to
``range`` that returns arrays instead of lists

>>> np.arange( 10, 30, 5 )
array([10, 15, 20, 25])
>>> np.arange( 0, 2, 0.3 )                 # it accepts float arguments
array([ 0. ,  0.3,  0.6,  0.9,  1.2,  1.5,  1.8])

[demo]

import numpy as np
print(np.arange( 10, 30, 5 ))
print(np.arange( 0, 2, 0.3 ) )

[/demo]


When ``arange`` is used with floating point arguments, it is generally
not possible to predict the number of elements obtained, due to the
finite floating point precision. For this reason, it is usually better
to use the function ``linspace`` that receives as an argument the number
of elements that we want, instead of the step:

>>> from numpy import pi
>>> np.linspace( 0, 2, 9 )                 # 9 numbers from 0 to 2
array([ 0.  ,  0.25,  0.5 ,  0.75,  1.  ,  1.25,  1.5 ,  1.75,  2.  ])
>>> x = np.linspace( 0, 2*pi, 100 )        # useful to evaluate function at lots of points
>>> f = np.sin(x)

[demo]

import numpy as np
from numpy import pi
print(np.linspace( 0, 2, 9 ))
x = np.linspace( 0, 2*pi, 100 )
print(x)
f = np.sin(x)
print(f)


[/demo]


.. seealso::
`array`,
`zeros`,
`zeros_like`,
`ones`,
`ones_like`,
`empty`,
`empty_like`,
`arange`,
`linspace`,
`numpy.random.rand`,
`numpy.random.randn`,
`fromfunction`,
`fromfile`

2.3 Printing Arrays
---------------

When you print an array, NumPy displays it in a similar way to nested
lists, but with the following layout:

-  the last axis is printed from left to right,
-  the second-to-last is printed from top to bottom,
-  the rest are also printed from top to bottom, with each slice
separated from the next by an empty line.

One-dimensional arrays are then printed as rows, bidimensionals as
matrices and tridimensionals as lists of matrices.

>>> a = np.arange(6)                         # 1d array
>>> print(a)
[0 1 2 3 4 5]
>>>
>>> b = np.arange(12).reshape(4,3)           # 2d array
>>> print(b)
[[ 0  1  2]
[ 3  4  5]
[ 6  7  8]
[ 9 10 11]]
>>>
>>> c = np.arange(24).reshape(2,3,4)         # 3d array
>>> print(c)
[[[ 0  1  2  3]
[ 4  5  6  7]
[ 8  9 10 11]]
[[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]]

[demo]

import numpy as np
a = np.arange(6)
print(a)
b = np.arange(12).reshape(4,3)
print(b)
c = np.arange(24).reshape(2,3,4)
print(c)

[/demo]


See :ref:`below <quickstart.shape-manipulation>` to get
more details on ``reshape``.

If an array is too large to be printed, NumPy automatically skips the
central part of the array and only prints the corners:

>>> print(np.arange(10000))
[   0    1    2 ..., 9997 9998 9999]
>>>
>>> print(np.arange(10000).reshape(100,100))
[[   0    1    2 ...,   97   98   99]
[ 100  101  102 ...,  197  198  199]
[ 200  201  202 ...,  297  298  299]
...,
[9700 9701 9702 ..., 9797 9798 9799]
[9800 9801 9802 ..., 9897 9898 9899]
[9900 9901 9902 ..., 9997 9998 9999]]

[demo]

import numpy as np
print(np.arange(10000))
print(np.arange(10000).reshape(100,100))

[/demo]


To disable this behaviour and force NumPy to print the entire array, you
can change the printing options using ``set_printoptions``.

::

>>> np.set_printoptions(threshold='nan')



2.4 Basic Operations
----------------

Arithmetic operators on arrays apply *elementwise*. A new array is
created and filled with the result.

>>> a = np.array( [20,30,40,50] )
>>> b = np.arange( 4 )
>>> b
array([0, 1, 2, 3])
>>> c = a-b
>>> c
array([20, 29, 38, 47])
>>> b**2
array([0, 1, 4, 9])
>>> 10*np.sin(a)
array([ 9.12945251, -9.88031624,  7.4511316 , -2.62374854])
>>> a<35
array([ True, True, False, False], dtype=bool)

[demo]

import numpy as np
a = np.array( [20,30,40,50] )
b = np.arange( 4 )
print(b)
c = a-b
print(c)
print(b**2)
print(10*np.sin(a))
print(a<35)

[/demo]


Unlike in many matrix languages, the product operator ``*`` operates
elementwise in NumPy arrays. The matrix product can be performed using
the ``dot`` function or method:

>>> A = np.array( [[1,1],
...             [0,1]] )
>>> B = np.array( [[2,0],
...             [3,4]] )
>>> A*B                         # elementwise product
array([[2, 0],
[0, 4]])
>>> A.dot(B)                    # matrix product
array([[5, 4],
[3, 4]])
>>> np.dot(A, B)                # another matrix product
array([[5, 4],
[3, 4]])

[demo]

import numpy as np
A = np.array( [[1,1],[0,1]] )
B = np.array( [[2,0],[3,4]] )
print(A*B)
print(A.dot(B))
print(np.dot(A, B))

[/demo]


Some operations, such as ``+=`` and ``*=``, act in place to modify an
existing array rather than create a new one.

>>> a = np.ones((2,3), dtype=int)
>>> b = np.random.random((2,3))
>>> a *= 3
>>> a
array([[3, 3, 3],
[3, 3, 3]])
>>> b += a
>>> b
array([[ 3.417022  ,  3.72032449,  3.00011437],
[ 3.30233257,  3.14675589,  3.09233859]])
>>> a += b                  # b is not automatically converted to integer type
Traceback (most recent call last):
...
TypeError: Cannot cast ufunc add output from dtype('float64') to dtype('int64') with casting rule 'same_kind'

[demo]

import numpy as np
a = np.ones((2,3), dtype=int)
b = np.random.random((2,3))
a *= 3
print(a)
b += a
print(b)
a += b

[/demo]


When operating with arrays of different types, the type of the resulting
array corresponds to the more general or precise one (a behavior known
as upcasting).

>>> a = np.ones(3, dtype=np.int32)
>>> b = np.linspace(0,pi,3)
>>> b.dtype.name
'float64'
>>> c = a+b
>>> c
array([ 1.        ,  2.57079633,  4.14159265])
>>> c.dtype.name
'float64'
>>> d = np.exp(c*1j)
>>> d
array([ 0.54030231+0.84147098j, -0.84147098+0.54030231j,
-0.54030231-0.84147098j])
>>> d.dtype.name
'complex128'

[demo]

import numpy as np
a = np.ones(3, dtype=np.int32)
b = np.linspace(0,pi,3)
print(b.dtype.name)
c = a+b
print(c)
print(c.dtype.name)
d = np.exp(c*1j)
print(d)
print(d.dtype.name)

[/demo]


Many unary operations, such as computing the sum of all the elements in
the array, are implemented as methods of the ``ndarray`` class.

>>> a = np.random.random((2,3))
>>> a
array([[ 0.18626021,  0.34556073,  0.39676747],
[ 0.53881673,  0.41919451,  0.6852195 ]])
>>> a.sum()
2.5718191614547998
>>> a.min()
0.1862602113776709
>>> a.max()
0.6852195003967595

[demo]

import numpy as np
a = np.random.random((2,3))
print(a)
print(a.sum())
print(a.min())
print(a.max())

[/demo]


By default, these operations apply to the array as though it were a list
of numbers, regardless of its shape. However, by specifying the ``axis``
parameter you can apply an operation along the specified axis of an
array:

>>> b = np.arange(12).reshape(3,4)
>>> b
array([[ 0,  1,  2,  3],
[ 4,  5,  6,  7],
[ 8,  9, 10, 11]])
>>>
>>> b.sum(axis=0)                            # sum of each column
array([12, 15, 18, 21])
>>>
>>> b.min(axis=1)                            # min of each row
array([0, 4, 8])
>>>
>>> b.cumsum(axis=1)                         # cumulative sum along each row
array([[ 0,  1,  3,  6],
[ 4,  9, 15, 22],
[ 8, 17, 27, 38]])

[demo]

import numpy as np
b = np.arange(12).reshape(3,4)
print(b)
print(b.sum(axis=0) )
print(b.min(axis=1) )
print(b.cumsum(axis=1))

[/demo]



2.5 Universal Functions
-------------------

NumPy provides familiar mathematical functions such as sin, cos, and
exp. In NumPy, these are called "universal
functions"(\ ``ufunc``). Within NumPy, these functions
operate elementwise on an array, producing an array as output.

>>> B = np.arange(3)
>>> B
array([0, 1, 2])
>>> np.exp(B)
array([ 1.        ,  2.71828183,  7.3890561 ])
>>> np.sqrt(B)
array([ 0.        ,  1.        ,  1.41421356])
>>> C = np.array([2., -1., 4.])
>>> np.add(B, C)
array([ 2.,  0.,  6.])

[demo]

import numpy as np
B = np.arange(3)
print(B)
print(np.exp(B))
print(np.sqrt(B))
C = np.array([2., -1., 4.])
print(np.add(B, C))

[/demo]


.. seealso::

`all`,
`any`,
`apply_along_axis`,
`argmax`,
`argmin`,
`argsort`,
`average`,
`bincount`,
`ceil`,
`clip`,
`conj`,
`corrcoef`,
`cov`,
`cross`,
`cumprod`,
`cumsum`,
`diff`,
`dot`,
`floor`,
`inner`,
`inv`,
`lexsort`,
`max`,
`maximum`,
`mean`,
`median`,
`min`,
`minimum`,
`nonzero`,
`outer`,
`prod`,
`re`,
`round`,
`sort`,
`std`,
`sum`,
`trace`,
`transpose`,
`var`,
`vdot`,
`vectorize`,
`where`

2.6 Indexing, Slicing and Iterating
-------------------------------

**One-dimensional** arrays can be indexed, sliced and iterated over,
much like
`lists <https://docs.python.org/tutorial/introduction.html#lists>`__
and other Python sequences.

>>> a = np.arange(10)**3
>>> a
array([  0,   1,   8,  27,  64, 125, 216, 343, 512, 729])
>>> a[2]
8
>>> a[2:5]
array([ 8, 27, 64])
>>> a[:6:2] = -1000    # equivalent to a[0:6:2] = -1000; from start to position 6, exclusive, set every 2nd element to -1000
>>> a
array([-1000,     1, -1000,    27, -1000,   125,   216,   343,   512,   729])
>>> a[ : :-1]                                 # reversed a
array([  729,   512,   343,   216,   125, -1000,    27, -1000,     1, -1000])
>>> for i in a:
...     print(i**(1/3.))
...
nan
1.0
nan
3.0
nan
5.0
6.0
7.0
8.0
9.0

[demo]

import numpy as np
a = np.arange(10)**3
print(a)
print(a[2])
print(a[2:5])
a[:6:2] = -1000
print(a)
print(a[ : :-1])
for i in a:
    print(i**(1/3.))


[/demo]


**Multidimensional** arrays can have one index per axis. These indices
are given in a tuple separated by commas:

>>> def f(x,y):
...     return 10*x+y
...
>>> b = np.fromfunction(f,(5,4),dtype=int)
>>> b
array([[ 0,  1,  2,  3],
[10, 11, 12, 13],
[20, 21, 22, 23],
[30, 31, 32, 33],
[40, 41, 42, 43]])
>>> b[2,3]
23
>>> b[0:5, 1]                       # each row in the second column of b
array([ 1, 11, 21, 31, 41])
>>> b[ : ,1]                        # equivalent to the previous example
array([ 1, 11, 21, 31, 41])
>>> b[1:3, : ]                      # each column in the second and third row of b
array([[10, 11, 12, 13],
[20, 21, 22, 23]])

[demo]

import numpy as np
def f(x,y):
    return 10*x+y

b = np.fromfunction(f,(5,4),dtype=int)
print(b)
print(b[2,3])
print(b[0:5, 1])
print(b[ : ,1])
print(b[1:3, : ])
print(b[-1] )
for row in b:
    print(row)

for element in b.flat:
    print(element)

[/demo]


When fewer indices are provided than the number of axes, the missing
indices are considered complete slices\ ``:``

>>> b[-1]                                  # the last row. Equivalent to b[-1,:]
array([40, 41, 42, 43])

The expression within brackets in ``b[i]`` is treated as an ``i``
followed by as many instances of ``:`` as needed to represent the
remaining axes. NumPy also allows you to write this using dots as
``b[i,...]``.

The **dots** (``...``) represent as many colons as needed to produce a
complete indexing tuple. For example, if ``x`` is a rank 5 array (i.e.,
it has 5 axes), then

-  ``x[1,2,...]`` is equivalent to ``x[1,2,:,:,:]``,
-  ``x[...,3]`` to ``x[:,:,:,:,3]`` and
-  ``x[4,...,5,:]`` to ``x[4,:,:,5,:]``.

>>> c = np.array( [[[  0,  1,  2],               # a 3D array (two stacked 2D arrays)
...                 [ 10, 12, 13]],
...                [[100,101,102],
...                 [110,112,113]]])
>>> c.shape
(2, 2, 3)
>>> c[1,...]                                   # same as c[1,:,:] or c[1]
array([[100, 101, 102],
[110, 112, 113]])
>>> c[...,2]                                   # same as c[:,:,2]
array([[  2,  13],
[102, 113]])

[demo]

import numpy as np
c = np.array( [[[  0,  1,  2],               # a 3D array (two stacked 2D arrays)
                [ 10, 12, 13]],
               [[100,101,102],
                [110,112,113]]])
print(c.shape)
print(c[1,...] )
print(c[...,2] )

[/demo]


**Iterating** over multidimensional arrays is done with respect to the
first axis:

>>> for row in b:
...     print(row)
...
[0 1 2 3]
[10 11 12 13]
[20 21 22 23]
[30 31 32 33]
[40 41 42 43]

However, if one wants to perform an operation on each element in the
array, one can use the ``flat`` attribute which is an
`iterator <https://docs.python.org/2/tutorial/classes.html#iterators>`__
over all the elements of the array:

>>> for element in b.flat:
...     print(element)
...
0
1
2
3
10
11
12
13
20
21
22
23
30
31
32
33
40
41
42
43

.. seealso::

:ref:`basics.indexing`,
:ref:`arrays.indexing` (reference),
`newaxis`,
`ndenumerate`,
`indices`

.. _quickstart.shape-manipulation: